#include "../system.h"

#ifdef XG_LINUX

#include <pwd.h>
#include <utime.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>
#include <sys/sysinfo.h>

#define ioctlsocket ioctl

#ifndef __USE_LARGEFILE64
#define __USE_LARGEFILE64
#endif

#ifndef __USE_FILE_OFFSET64
#define __USE_FILE_OFFSET64
#endif

#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif

#define syscode(path) path

#else

#define syscode(path) syspath(path).val

static stCharBuffer syspath(const char* str)
{
	stCharBuffer res;
	int len = strlen(str);

	if (len >= sizeof(res.val))
	{
		strncpy(res.val, str, sizeof(res.val) - 1);

		return res;
	}

	len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);

	if (len <= 0 || len >= sizeof(res.val))
	{
		strncpy(res.val, str, sizeof(res.val) - 1);

		return res;
	}

	wchar_t wstr[sizeof(res.val) + 1];
	MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);
	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);

	if (len <= 0 || len >= sizeof(res.val))
	{
		strncpy(res.val, str, sizeof(res.val) - 1);
	}
	else
	{
		WideCharToMultiByte(CP_ACP, 0, wstr, -1, res.val, len, NULL, NULL);
		res.val[len] = 0;
	}

	return res;
}

#endif

int System(const char* cmd)
{
#ifdef XG_LINUX
	return system(syscode(cmd));
#else
	char buffer[sizeof(stCharBuffer)];

	snprintf(buffer, sizeof(buffer), "\"%s\"", syscode(cmd));

	return system(buffer);
#endif
}
const char* GetCurrentUser()
{
	static char name[0xFF];

	if (*name) return name;
	
#ifdef XG_LINUX
	struct passwd* pwd;
	
	if ((pwd = getpwuid(getuid()))) strncpy(name, syscode(pwd->pw_name), sizeof(name) - 1);
#else
	DWORD sz = sizeof(name) - 1;

	if (GetUserNameA(name, &sz)) strncpy(name, syscode(name), sizeof(name) - 1);
#endif

	if (*name == 0) ErrorExit(XG_SYSERR);
	
	return name;
}

int RemoveFile(const char* path)
{
	return remove(syscode(path));
}

long GetFileLength(const char* path)
{
#ifdef XG_LINUX
	struct stat info;

	if (stat(syscode(path), &info) < 0) return XG_NOTFOUND;

	if (S_ISDIR(info.st_mode)) return XG_PARAMERR;

	return info.st_size;
#else
	if (GetPathType(path) == eNONE) return XG_NOTFOUND;

	long sz = 0;
	long ofs = 0;
	FILE* fp = fopen(syscode(path), "rb");

	if (fp == NULL) return XG_PARAMERR;

	ofs = ftell(fp);
	fseek(fp, 0, SEEK_END);
	sz = ftell(fp);
	fseek(fp, ofs, SEEK_SET);
	fclose(fp);

	return sz;
#endif
}

int CreateFolder(const char* path)
{
	int i = 0;
	int len = strlen(path);
	char szDirName[MAX_PATH] = {0};

	if (len >= MAX_PATH) return XG_PARAMERR;
	if (GetPathType(path) == ePATH) return XG_OK;

	if (path[len - 1] == '/')
	{
		memcpy(szDirName, path, len);
	}
	else
	{
		memcpy(szDirName, path, len);

		szDirName[len++] = '/';
	}

	for (i = 1; i < len; i++)
	{
		if (szDirName[i] == '/')
		{
			szDirName[i] = 0;
#ifdef XG_LINUX
			if (access(syscode(szDirName), F_OK))
			{
				if (mkdir(syscode(szDirName), 0755) < 0) return XG_ERROR;
			}
#else
			if (GetPathType(szDirName) == eNONE) CreateDirectoryA(syscode(szDirName), NULL);
#endif
			szDirName[i] = '/';
		}
	}

	return 0;
}

int Rename(const char* src, const char* dest)
{
	return rename(syscode(src), syscode(dest));
}

int CloneFile(const char* src, const char* dest)
{
	int len = 0;
	long sum = 0;
	HANDLE srcfile;
	HANDLE destfile;

	if ((sum = GetFileLength(src)) >= 0 && HandleCanUse(srcfile = Open(src, eREAD)))
	{
#ifndef XG_LINUX
		RemoveFile(dest);
#endif
		if (HandleCanUse(destfile = Open(dest, eCREATE)))
		{
			long readed = 0;
			char buffer[64 * 1024];

			while ((len = sum - readed) > 0)
			{
				if (len > sizeof(buffer)) len = sizeof(buffer);
				if ((len = Read(srcfile, buffer, len)) <= 0) break;
				if ((len = Write(destfile, buffer, len)) <= 0) break;

				readed += len;
			}

			Close(destfile);
		}
		else
		{
			len = XG_IOERR;
		}

		Close(srcfile);
	}

	return len < 0 ? len : sum; 
}

void ColorPrint(E_CONSOLE_COLOR color, const char* fmt, ...)
{
	va_list args;
	char buffer[sizeof(stCharBuffer)];

	va_start(args, fmt);
	vsnprintf(buffer, sizeof(buffer), fmt, args);
	buffer[sizeof(buffer) - 1] = 0;
	va_end(args);

	SetConsoleTextColor(color);
	printf("%s", syscode(buffer));
	SetConsoleTextColor(eWHITE);

	fflush(stdout);
}

#ifdef XG_LINUX

#ifndef O_LARGEFILE
#define O_LARGEFILE	0
#endif

void Close(HANDLE handle)
{
	close(handle);
}

HANDLE Open(const char* filename, E_OPEN_MODE mode)
{
	if (GetPathType(filename) == ePATH) return INVALID_HANDLE_VALUE;

	if (mode == eCREATE) return open(syscode(filename), O_RDWR | O_LARGEFILE | O_CREAT | O_TRUNC, XG_IPC_FLAG);

	if (mode == eREAD) return open(syscode(filename), O_RDONLY | O_LARGEFILE);

	return open(syscode(filename), O_RDWR | O_LARGEFILE);
}

long long Seek(HANDLE handle, long long offset, int mode)
{
	return lseek(handle, offset, mode);
}

int Read(HANDLE handle, void* data, int size)
{
	return read(handle, data, size);
}

int Write(HANDLE handle, const void* data, int size)
{
	return write(handle, data, size);
}

int GetPathType(const char* path)
{
	struct stat info;

	if (stat(syscode(path), &info) < 0) return eNONE;

	return S_ISDIR(info.st_mode) ? ePATH : eFILE;
}

BOOL SetPathAttributes(const char* path, E_OPEN_MODE mode)
{
	if (eREAD == mode) return chmod(syscode(path), S_IRUSR) >= 0 ? TRUE : FALSE;

	if (eWRITE == mode) return chmod(syscode(path), S_IRUSR | S_IWUSR) >= 0 ? TRUE : FALSE;

	return FALSE;
}

time_t GetFileUpdateTime(const char* path)
{
	struct stat info;

	return stat(syscode(path), &info) == 0 ? info.st_mtime : XG_FAIL;
}

BOOL SetFileUpdateTime(const char* path, const time_t* tm)
{
	time_t now;
	struct utimbuf tmp;

	if (tm == NULL)
	{
		time(&now);
		tm = &now;
	}

	tmp.modtime = *tm;
	tmp.actime = *tm;

	return utime(syscode(path), &tmp) == 0;
}

#else

void Close(HANDLE handle)
{
	CloseHandle(handle);
}

HANDLE Open(const char* filename, E_OPEN_MODE mode)
{
	if (GetPathType(filename) == ePATH) return INVALID_HANDLE_VALUE;

	if (mode == eREAD) return CreateFileA(syscode(filename), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

	return CreateFileA(syscode(filename), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, mode == eCREATE ? CREATE_ALWAYS : OPEN_EXISTING, 0, NULL);
}

long long Seek(HANDLE handle, long long offset, int mode)
{
	LARGE_INTEGER large;

	memset(&large, 0, sizeof(LARGE_INTEGER));

	large.QuadPart = offset;

	if (SetFilePointerEx(handle, large, &large, mode)) return large.QuadPart;

	return XG_ERROR;
}

int Read(HANDLE handle, void* data, int size)
{
	DWORD readed = 0;

	if (ReadFile(handle, data, (DWORD)(size), &readed, NULL)) return readed;

	return XG_ERROR;
}

int Write(HANDLE handle, const void* data, int size)
{
	DWORD writed = 0;

	if (WriteFile(handle, data, (DWORD)(size), &writed, NULL)) return writed;

	return XG_ERROR;
}

int GetPathType(const char* path)
{
	DWORD flag = GetFileAttributesA(syscode(path));

	if (flag == INVALID_FILE_ATTRIBUTES) return eNONE;

	return (flag & FILE_ATTRIBUTE_DIRECTORY) ? ePATH : eFILE;
}

BOOL SetPathAttributes(const char* path, E_OPEN_MODE mode)
{
	DWORD flag = 0;

	if (mode == eREAD)
	{
		flag = FILE_ATTRIBUTE_READONLY;
	}
	else if (mode == eWRITE)
	{
		flag = FILE_ATTRIBUTE_NORMAL;
	}
	else if (mode == eHIDDEN)
	{
		flag = GetFileAttributesA(syscode(path));

		if (flag == INVALID_FILE_ATTRIBUTES) return FALSE;

		flag |= FILE_ATTRIBUTE_HIDDEN;
	}
	else if (mode == eVISIBLE)
	{
		flag = GetFileAttributesA(syscode(path));

		if (flag == INVALID_FILE_ATTRIBUTES) return FALSE;

		if ((flag & FILE_ATTRIBUTE_HIDDEN) == 0) return TRUE;

		flag ^= FILE_ATTRIBUTE_HIDDEN;
	}

	return SetFileAttributesA(syscode(path), flag);
}

time_t GetFileUpdateTime(const char* path)
{
	HANDLE handle = CreateFileA(syscode(path), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

	if (handle)
	{
		struct tm t;
		FILETIME ft;
		SYSTEMTIME st;
		SYSTEMTIME utc;

		if (GetFileTime(handle, NULL, NULL, &ft) && FileTimeToSystemTime(&ft, &utc))
		{
			SystemTimeToTzSpecificLocalTime(NULL, &utc, &st);

			t.tm_year = st.wYear - 1900;
			t.tm_mon = st.wMonth - 1;
			t.tm_mday = st.wDay;

			t.tm_sec = st.wSecond;
			t.tm_min = st.wMinute;
			t.tm_hour = st.wHour;

			CloseHandle(handle);

			return mktime(&t);
		}

		CloseHandle(handle);
	}

	return XG_FAIL;
}

BOOL SetFileUpdateTime(const char* path, const time_t* tm)
{
	BOOL res;
	HANDLE file;
	FILETIME ft;
	stDateTime dt;
	SYSTEMTIME st;
	SYSTEMTIME utc;

	GetDateTime(&dt, tm);

	st.wDayOfWeek = dt.wday;
	st.wMilliseconds = 500;
	st.wMonth = dt.month;
	st.wMinute = dt.min;
	st.wSecond = dt.sec;
	st.wYear = dt.year;
	st.wHour = dt.hour;
	st.wDay = dt.day;

	file = Open(path, eWRITE);

	CHECK_FALSE_RETURN(HandleCanUse(file));

	TzSpecificLocalTimeToSystemTime(NULL, &st, &utc);
	SystemTimeToFileTime(&utc, &ft);

	res = SetFileTime(file, NULL, &ft, &ft);

	Close(file);

	return res;
}

#endif

long long Tell(HANDLE handle)
{
	return Seek(handle, 0, SEEK_CUR);
}

void Flush(HANDLE handle)
{
#ifndef XG_LINUX
	FlushFileBuffers(handle);
#endif
}

int GetCpuCount()
{
	static int num = 0;

	if (num == 0)
	{
#ifdef XG_LINUX
		num = get_nprocs();
#else
		SYSTEM_INFO info;
		GetSystemInfo(&info);
		num = info.dwNumberOfProcessors;
#endif
	}

	return num;
}

BOOL GetCpuTime(long long* usr, long long* idle, long long* kernel)
{
#ifdef XG_LINUX
	FILE* fp = fopen(syscode("/proc/stat"), "r");

	CHECK_FALSE_RETURN(fp);

	char tmp[1024] = {0};
	char buffer[1024] = {0};
	long long val = fread(buffer, sizeof(buffer) - 128, sizeof(char), fp);

	fclose(fp);
	strcat(buffer, " 0 0 0 0 0 0 ");
	sscanf(buffer, "%s %lld %lld %lld %lld", tmp, usr, &val, kernel, idle);

	*kernel += *idle + val;
#else
	FILETIME usrtime;
	FILETIME idletime;
	FILETIME kerneltime;

	CHECK_FALSE_RETURN(GetSystemTimes(&idletime, &kerneltime, &usrtime));

	*usr = (((long long)(usrtime.dwHighDateTime) << 32) | usrtime.dwLowDateTime) / 10;
	*idle = (((long long)(idletime.dwHighDateTime) << 32) | idletime.dwLowDateTime) / 10;
	*kernel = (((long long)(kerneltime.dwHighDateTime) << 32) | kerneltime.dwLowDateTime) / 10;
#endif
	return TRUE;
}

BOOL GetDiskSpace(const char* path, long long* total, long long* avail, long long* free)
{
#ifdef XG_LINUX
	struct statfs stat;

	if (path == NULL) path = "/";

	if (statfs(syscode(path), &stat) < 0) return FALSE;

	*free = stat.f_bfree * stat.f_bsize;
	*total = stat.f_blocks * stat.f_bsize;
	*avail = stat.f_bavail * stat.f_bsize;

	return TRUE;
#else
	if (path == NULL)
	{
		int num = 0;
		char str[] = "X:/";
		long long a = 0, t = 0, f = 0;

		*free = 0;
		*avail = 0;
		*total = 0;

		for (char ch = 'A'; ch <= 'Z'; ch++)
		{
			*str = ch;

			if (GetDiskFreeSpaceExA(syscode(str), (PULARGE_INTEGER)(&a), (PULARGE_INTEGER)(&t), (PULARGE_INTEGER)(&f)))
			{
				num = 0;
				*free += f;
				*avail += a;
				*total += t;
			}
			else
			{
				if (++num > 4) break;
			}
		}

		return *total > 0 ? TRUE : FALSE;
	}

	return GetDiskFreeSpaceExA(syscode(path), (PULARGE_INTEGER)(avail), (PULARGE_INTEGER)(total), (PULARGE_INTEGER)(free));
#endif
}

int CalcCpuUseage(int delay)
{
	long long usr, idle, kernel;
	long long preusr, preidle, prekernel;

	GetCpuTime(&preusr, &preidle, &prekernel);

	Sleep(delay);

	GetCpuTime(&usr, &idle, &kernel);

	usr -= preusr;
	idle -= preidle;
	kernel -= prekernel;

	return kernel + usr > 0 ? (kernel + usr - idle) * 100 / (kernel + usr) : 0;
}

BOOL GetMemoryInfo(long long* total, long long* avail)
{
#ifdef XG_LINUX
	struct sysinfo info;

	CHECK_FALSE_RETURN(sysinfo(&info) >= 0);

	if (total) *total = info.totalram;
	if (avail) *avail = info.freeram;
#else
	MEMORYSTATUSEX info;

	info.dwLength = sizeof(MEMORYSTATUSEX);

	CHECK_FALSE_RETURN(GlobalMemoryStatusEx(&info));

	if (total) *total = info.ullTotalPhys;
	if (avail) *avail = info.ullAvailPhys;
#endif
	return TRUE;
}

BOOL ReadSector(HANDLE handle, void* data, long long offset, long long num)
{
	offset *= 512;

	CHECK_FALSE_RETURN(Seek(handle, offset, SEEK_SET) == offset);

	num *= 512;

	return Read(handle, data, num) == num ? TRUE : FALSE;
}

BOOL WriteSector(HANDLE handle, const void* data, long long offset, long long num)
{
	offset *= 512;

	CHECK_FALSE_RETURN(Seek(handle, offset, SEEK_SET) == offset);

	num *= 512;

	return Write(handle, data, num) == num ? TRUE : FALSE;
}

HANDLE OpenDisk(int diskno, E_OPEN_MODE mode)
{
#ifdef XG_LINUX
	char str[] = "/dev/sd#";

	diskno += 'a';

	if (diskno > 'z') return (HANDLE)(-1);

	str[strlen(str) - 1] = (char)(diskno);
#else
	char str[] = "\\\\.\\PHYSICALDRIVE##";

	size_t len = strlen(str);

	if (diskno < 10)
	{
		str[len - 2] = (char)('0' + diskno);
		str[len - 1] = 0;
	}
	else if (diskno < 100)
	{
		str[len - 2] = (char)('0' + diskno / 10);
		str[len - 1] = (char)('0' + diskno % 10);
	}
	else
	{
		return (HANDLE)(-1);
	}
#endif

	return Open(str, mode);
}

HANDLE OpenPartition(int ch, E_OPEN_MODE mode)
{
#ifdef XG_LINUX
	char str[] = "/dev/sda#";

	ch -= (ch > 'Z') ? 'a' : 'A';

	if (ch > 10) return (HANDLE)(-1);

	str[strlen(str) - 1] = (char)(ch + '1');
#else
	char str[] = "\\\\.\\#:";

	if (ch > 'Z') ch -= 'z' - 'Z';

	str[strlen(str) - 2] = (char)(ch);
#endif

	return Open(str, mode);
}

typedef struct
{
	void* data;
	void(*func)(void*);
} stWorkItem;

#ifdef XG_LINUX

void* ThreadFunction(void* param)
{
	stWorkItem* item = (stWorkItem*)(param);

	item->func(item->data);
	free(item);

	pthread_detach(pthread_self());

	return NULL;
}

#else

DWORD WINAPI ThreadFunction(void* param)
{
	stWorkItem* item = (stWorkItem*)(param);

	item->func(item->data);
	free(item);

	return 0;
}
#endif

BOOL StartThread(void(*func)(void*), void* data)
{
	THREAD_T thread;
	stWorkItem* param = (stWorkItem*)malloc(sizeof(stWorkItem));

	if (param == NULL) return FALSE;

	param->data = data;
	param->func = func;

#ifdef XG_LINUX
	if (pthread_create(&thread, NULL, ThreadFunction, param))
	{
		free(param);

		return FALSE;
	}
#else
	thread = CreateThread(NULL, 0, ThreadFunction, param, 0, NULL);

	if (thread == (THREAD_T)(-1) || thread == (THREAD_T)(NULL))
	{
		free(param);

		return FALSE;
	}
#endif

	return TRUE;
}

void DllFileClose(DLLFILE_T handle)
{
#ifdef XG_LINUX
	dlclose(handle);
#else
	FreeLibrary((HMODULE)(handle));
#endif
}

DLLFILE_T DllFileOpen(const char* path)
{
#ifdef XG_LINUX
	return dlopen(syscode(path), RTLD_NOW);
#else
	return (DLLFILE_T)(LoadLibraryA(syscode(path)));
#endif
}

void* DllFileGetAddress(DLLFILE_T handle, const char* name)
{
#ifdef XG_LINUX
	return (void*)(dlsym(handle, name));
#else
	return (void*)(GetProcAddress((HMODULE)(handle), name));
#endif
}